using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using XYThreadPoolLib;
using System.Diagnostics;
using Magnolia;

namespace CommLayer
{
    class InputProcessorMedialon : InputProcessor
    {

        public byte m_prefix;
        public byte m_suffix;
    
        /// <summary>
        /// Constructor. Processes all the input, medialon protocol
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="suffix"></param>
        public InputProcessorMedialon(byte prefix, byte suffix, StringInputHandlerDelegate delegateStringInputHandler,XYThreadPool threadPool
                        ) : 
                base (delegateStringInputHandler,threadPool)
        {
            m_prefix = prefix;
            m_suffix = suffix;
        }

        /// <summary>
        /// Extracts theString from the buffer, starting at position 0 till the suffix. 
        /// </summary>
        /// <param name="pData"></param>
        /// <param name="scanPos"></param>
        /// <param name="theString"></param>
        /// <returns>suffix found</returns>
        private bool ExtractString(byte[] pData, ref string theString)
        {
            bool suffixFound = true;
            // find suffix pos
            int suffixPos = XYNetCommon.FindByte(pData, 0, m_suffix);

            // if string was just ended
            if (suffixPos == 0)
            {
                theString = "";
                return suffixFound;
            }

            // if no suffix, just get till the end of the buffer
            if (suffixPos < 0)
            {
                suffixPos = pData.Length; // 'as if' the suffix is just right out there one position beyond the end of the pData stream
                suffixFound = false;
            }

            //// get the (rest) of this string
            //for (int i=0; i < suffixPos; i++)
            //{
            //    theStringBuilder.Append(pData[i]);
            //}
            Byte[] stringdata = new byte[suffixPos];
            Array.Copy(pData, 0, stringdata, 0, stringdata.Length);
            theString = System.Text.Encoding.ASCII.GetString(stringdata);

            // return next position
            return suffixFound;
        }

        /// <summary>
        /// Helper function to process all the input, medialon protocol
        /// </summary>
        /// <param name="sock"></param>
        /// <param name="ipe"></param>
        public override void ProcessInput(Transmission trn, IPEndPoint ipe)
        {

            Socket sock = trn.m_socket;

            try
            {
               
                string theString = "";
                int nPos = 0;
                int nSizeAvailble = 0;
                long nStart = DateTime.Now.Ticks;

                // TODO: try loop (while !sock.Available)
                if ((nSizeAvailble = sock.Available) > 0)
                {
                    Trace.WriteLineIf(AppContext.AppTraceSwitch.TraceError,  DateTime.Now.ToLongTimeString() + "-> " + XYNetCommon.GetTimeString() + "Bytes available:" + nSizeAvailble.ToString());
                    if (nSizeAvailble > m_nMaxDataSize)
                        throw new Exception("Data size too large");

                    Byte[] pData = new Byte[nSizeAvailble];
                    nPos += sock.Receive(pData, 0, nSizeAvailble, SocketFlags.None);
                    if (nPos < 1)
                    {
                        throw new Exception("connection closed");
                    }

                    int scanPos = 0;
                    while (scanPos < pData.Length)
                    {
                        bool suffixFound = true;
                        // if looking for the prefix (m_message == "" only when looking for the prefix)
                        if (trn.m_message.Length == 0)
                        {
                            Debug.Assert(pData[scanPos] == m_prefix);
                            scanPos++;
                        }

                        // create a new buffer, starting at 0
                        byte[] workbuffer = new byte[pData.Length - scanPos];
                        Array.Copy(pData, scanPos, workbuffer, 0, workbuffer.Length);

                        // extract the string till the suffix 
                        suffixFound = ExtractString(workbuffer, ref theString);
                        //update next scan pos  
                        scanPos = scanPos + theString.Length;
                        if (suffixFound)
                            scanPos++;
                        //aggreagte the message
                        trn.m_message.Append( theString );
                        Trace.WriteLineIf(AppContext.AppTraceSwitch.TraceInfo,  DateTime.Now.ToLongTimeString() + "-> " + XYNetCommon.GetTimeString() + "Data received with len:" + trn.m_message.Length);
                        // And report when the time comes
                        if (suffixFound)
                        {
                            Trace.WriteLineIf(AppContext.AppTraceSwitch.TraceInfo, DateTime.Now.ToLongTimeString() + "-> Message received with len:" + trn.m_message.Length);

                            Debug.Assert(m_delegateStringInputHandler != null);
                            
                            string strMessage = trn.m_message.ToString();
                            Trace.WriteLineIf(AppContext.AppTraceSwitch.TraceInfo, DateTime.Now.ToLongTimeString() + "-> Message first 255 chars:" + strMessage.Substring(0, strMessage.Length >= 255 ? 255 : strMessage.Length));

                            m_threadPool.InsertWorkItem("Handle String Input",
                                new StringInputHandlerDelegate(m_delegateStringInputHandler), new Object[3] { ipe.Address.ToString(), ipe.Port, trn.m_message.ToString() }, false);
                            //m_delegateStringInputHandler(ipe.Address.ToString(), ipe.Port, trn.m_message);
                            // we sent the message, reset
                            trn.m_message = new StringBuilder(128);
                        }
                    }

                }
                else // if not availble, lets sleep for the wait 
                {
                    Thread.Sleep(m_nPause);
                }

                // the following is meaningless when working with an (if) on the available above, instead of with a while
                if (nPos < 1 && ((DateTime.Now.Ticks - nStart) / 10000) > m_nReadTimeout * 1000)
                    throw new Exception("Timeout while receiving incoming data");

                EndInputProcessor(trn);

                trn.m_socket.Blocking = true;

            }
            catch (Exception oBug)
            {
                Trace.WriteLineIf(AppContext.AppTraceSwitch.TraceError,  DateTime.Now.ToLongTimeString() + "-> " + oBug.Message);

                HandleException(oBug,trn);
            }
            finally { 
                // be extra carefull here since we are not surronded by try and catch
                //trn.m_socket.Blocking = true;

                
            } 
        }


        /// <summary>
        /// Handle exception
        /// </summary>
        /// <param name="ipe"></param>
        public virtual void HandleException(Exception oBug, Transmission trn)
        {
            // trace from here too
            Trace.WriteLineIf(AppContext.AppTraceSwitch.TraceError, DateTime.Now.ToLongTimeString() + "-> InputProcessorMedialon should not arrive here: " + oBug.Message);

            // should not have arrived here
            Debug.Assert(false);
        }
    }
}
